﻿namespace EFS.RC.CC.Sessions.OfficeService.Customers
{
	/// <summary>
	/// Customer session
	/// </summary>
	internal abstract class Customer : EFS.RC.CC.Sessions.OfficeService.Participant
	{
		#region Composition

		// tone
		private EFS.RC.CC.Tools.Tone.Controler toneController = null;

		// End point
		private EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication = null;

		// office conference
		private EFS.RC.CC.Confereces.OfficeService officeServiceConference = null;

		// Id
		private System.Guid conversationGuid = System.Guid.NewGuid();

		// start time
		private System.DateTime startTime;

		// Players
		private System.Collections.Generic.List<EFS.RC.CC.Tools.Players.Audio> players = new System.Collections.Generic.List<EFS.RC.CC.Tools.Players.Audio>();

		/// <summary>
		/// Status
		/// </summary>
		public enum SessionStatus { Idle, Started, Terminated }

		/// <summary>
		/// Status
		/// </summary>
		public enum ConversationCommands { None, Hold, Resume }

		// Status
		private SessionStatus status = SessionStatus.Idle;
		
		// Commands
		private ConversationCommands lastCommand = ConversationCommands.None;
		private ConversationCommands executingCommand = ConversationCommands.None;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="customersOfficeServiceEndPointApplication"></param>
		/// <param name="OutsideAudioVideoCall"></param>
		public Customer(EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication)
		{
			// check
			EFS.Common.Verify.Reference(customersOfficeServiceEndPointApplication, "customersOfficeServiceEndPointApplication");

			// set locals
			this.customersOfficeServiceEndPointApplication = customersOfficeServiceEndPointApplication;

			// make office conference
			officeServiceConference = new EFS.RC.CC.Confereces.OfficeService(this);
		}

		#endregion

		#region Usage

		// Start time
		public System.DateTime StartTime { get { return startTime; } }

		/// <summary>
		/// End point model
		/// </summary>
		public EFS.RC.CC.Applications.EndPoints.OfficeService.Customers CustomersOfficeServiceEndPointApplication
		{
			get { return customersOfficeServiceEndPointApplication; }
		}

		/// <summary>
		/// Office conference
		/// </summary>
		public override EFS.RC.CC.Confereces.OfficeService OfficeServiceConference
		{
			get { return officeServiceConference; }
		}

		/// <summary>
		/// Conversation guid
		/// </summary>
		public System.Guid ConversationGuid
		{
			get { return conversationGuid; }
		}

		/// <summary>
		/// Customer uri
		/// </summary>
		public abstract System.String DisplayUri
		{
			get;
		}

		/// <summary>
		/// Display name
		/// </summary>
		public abstract System.String DisplayName
		{
			get;
		}

		/// <summary>
		/// Customer uri
		/// </summary>
		public abstract System.String CustomerUri
		{
			get;
		}

		/// <summary>
		/// Conversation on hold flag
		/// </summary>
		public System.Boolean OnHold { get { return lastCommand == ConversationCommands.Hold; } }

		/// <summary>
		/// Hold conversation
		/// </summary>
		public void Hold()
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "Hold", this); 

			// transit to hold
			TransitToStatus(ConversationCommands.Hold);

			// start music
			BeginStartHoldPlayer();
		}

		/// <summary>
		/// Resume conversation
		/// </summary>
		public void Resume()
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "Resume", this); 

			// transit to resume
			TransitToStatus(ConversationCommands.Resume);

			// stop music
			BeginStopPlayers();
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public override EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Startup, OnStartupComplete);
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public override EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		/// <summary>
		/// Get caller information message
		/// </summary>
		/// <returns></returns>
		public EFS.RC.CC.Messages.Customer.Caller GetCallerInformationMessage()
		{
			// caller
			EFS.RC.CC.Messages.Customer.Caller caller = new Messages.Customer.Caller();

			// set
			caller.SIP = CustomerUri;
			caller.Telephone = DisplayUri;
			caller.CustomerEndPointId = CustomersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.ID.Value;
		
			// done
			return caller;
		}

		/// <summary>
		/// Send tone
		/// </summary>
		/// <param name="tone"></param>
		public void SendTone(System.Int32 tone, System.Single volume)
		{
			// log
			EFS.EventLog.Log.LogInfo("SendTone", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "SendTone", this); 

			lock (syncAccess)
			{
				if (null == toneController)
				{
					throw new System.InvalidOperationException("Tone controller not started");
				}

				// send
				toneController.Send(tone, volume);
			}
		}

		/// Status
		public SessionStatus Status { get { return status; } }

		// check any active players
		public System.Boolean HasActivePlayers
		{ 
			get
			{
				lock (syncAccess)
				{
					// stop all playes if any
					foreach(EFS.RC.CC.Tools.Players.Audio player in players)
					{
						// stop
						if (player.Status == EFS.RC.CC.Tools.Players.Audio.MusicStatus.Started)
						{
							return true;
						}
					}
				}

				// none
				return false;
			}
		}

		/// <summary>
		/// Start dial out music
		/// </summary>
		public void BeginStartDialPlayer()
		{
			// start async
			BeginAsyncProcess2(StartDialPlayer);
		}

		/// <summary>
		/// Start music for hold
		/// </summary>
		/// <returns></returns>
		public void BeginStartHoldPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartHoldPlayer", this);

			// start akync
			BeginAsyncProcess2(StartHoldPlayer);
		}

		/// <summary>
		/// Start music for hunting agent
		/// </summary>
		/// <returns></returns>
		public void BeginStartHuntingPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartHuntingPlayer", this);

			// start akync
			BeginAsyncProcess2(StartHuntingPlayer);
		}

		/// <summary>
		/// Start music for hunting agent
		/// </summary>
		/// <returns></returns>
		public void BeginStartAfterHoursPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartAfterHoursPlayer", this);

			// start akync
			BeginAsyncProcess2(StartAfterHoursPlayer);
		}

		/// <summary>
		/// Start music for busy
		/// </summary>
		/// <returns></returns>
		public void BeginStartBusyPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartBusyPlayer", this);

			// start akync
			BeginAsyncProcess2(StartBusyPlayer);
		}

		/// <summary>
		/// Start misic for hunting agent
		/// </summary>
		/// <returns></returns>
		public void BeginStopPlayers()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStopMusic", this);

			// start akync
			BeginAsyncProcess2(StopPlayers);
		}

		#endregion

		#region Startup

		/// <summary>
		/// Mark start time
		/// </summary>
		/// <returns></returns>
		protected abstract System.DateTime MarkStart();

		/// <summary>
		/// Session startup
		/// </summary>
		private void Startup()
		{
			// log
			EFS.EventLog.Log.LogInfo("Startup", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != SessionStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle Customer Session can be started");
				}

				// started
				status = SessionStatus.Started;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "Startup", this); 

			// register for events
			officeServiceConference.AsinkEstablishedDelegate.Register(OnOfficeServiceConferenceEstablished);
			officeServiceConference.AsinkTerminatedDelegate.Register(OnOfficeServiceConferenceTerminated);

			// mark start time
			startTime = MarkStart();

			// start office conference
			officeServiceConference.BeginStartup();
		}

		/// <summary>
		/// Customer service office conference terminated
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnOfficeServiceConferenceEstablished(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// check mine
			EFS.Common.Verify.Condition(officeServiceConference.Equals(this.officeServiceConference), "Established conference is different");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnOfficeServiceConferenceEstablished", this); 

			// setup session
			BeginAsyncProcess2(SetupSession);	
		}

		/// <summary>
		/// Bring customer into office service conference
		/// </summary>
		protected abstract void SetupSession();

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Called when participant end point added
		/// </summary>
		protected override void ParticipantEndPointAdded()
		{
			// log
			EFS.EventLog.Log.LogInfo("Customer: ParticipantEndPointAdded", this);

			// init tone controler
			BeginAsyncProcess2(InitToneController);

			// kill all music, customer is in
			AsinkEstablishedDelegate.Start();
		}

		/// <summary>
		/// Called when participant end point removed
		/// </summary>
		protected override void ParticipantEndPointRemoved()
		{
			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnException", this);

			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnException", ex, this); 

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Outside call
		/// </summary>
		protected abstract Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall OutsideAudioVideoCall
		{
			get;
		}

		/// <summary>
		/// Inside call
		/// </summary>
		protected abstract Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall InsideAudioVideoCall
		{
			get;
		}

		/// <summary>
		/// Office conversation
		/// </summary>
		protected abstract Microsoft.Rtc.Collaboration.Conversation OfficeConversation
		{
			get;
		}

		/// <summary>
		/// Set new status
		/// </summary>
		/// <param name="status"></param>
		private void TransitToStatus(ConversationCommands newCommand)
		{
			// log
			EFS.EventLog.Log.LogInfo("TransitToStatus", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check current status
				if (executingCommand != lastCommand)
				{
					// we are in transit, just set new transit
					this.executingCommand = newCommand;
				}
				else
				{
					executingCommand = newCommand;
					switch (newCommand)
					{
						case ConversationCommands.Hold:
							{
								// hold conversation
								BeginAsyncProcess2(HoldConversation);

								// break
								break;
							}
						case ConversationCommands.Resume:
							{
								// resume conversation
								BeginAsyncProcess2(ResumeConversation);

								// break
								break;
							}
					}
				}
			}
		}

		/// <summary>
		/// End point is shutting down
		/// </summary>
		/// <param name="customerOfficeServiceEndPoint"></param>
		private void OnOfficeServiceEndPointShuttingDown(EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeServiceEndPointShuttingDown", this);

			// check
			EFS.Common.Verify.Reference(customersOfficeServiceEndPointApplication, "customersOfficeServiceEndPointApplication");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnOfficeServiceEndPointShuttingDown", this); 

			// check mine
			EFS.Common.Verify.Condition(this.customersOfficeServiceEndPointApplication.Equals(customersOfficeServiceEndPointApplication), "Terminated Office Service Customer EndPoint Application is different");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Customer service office conference terminated
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnOfficeServiceConferenceTerminated(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeServiceConferenceTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnOfficeServiceConferenceTerminated", this); 

			// check mine
			EFS.Common.Verify.Condition(officeServiceConference.Equals(this.officeServiceConference), "Terminated conference is different");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Tone received 
		/// </summary>
		/// <param name="tone"></param>
		/// <param name="volume"></param>
		private void OnToneReceived(System.Int32 tone, System.Single volume)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnToneReceived", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnToneReceived", this); 

			// send to operators
			officeServiceConference.SendOperatorsTone(tone, volume);

			// send to AVR
			officeServiceConference.SendAVRTone(tone, volume);
		}

		/// <summary>
		/// Start tone controller
		/// </summary>
		/// <param name="participantEndpoint"></param>
		protected void InitToneController()
		{
			// log
			EFS.EventLog.Log.LogInfo("InitToneController", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "InitToneController", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// EFS.EventLog.Log.LogExtended("MakeB2BCall: Entering lock", this);
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Only Started Custmer Session can MakeB2BCall");
				}

				// check
				if (null != toneController)
				{
					throw new System.InvalidOperationException("Tone Controller already started");
				}

				// make controller
				toneController = new EFS.RC.CC.Tools.Tone.Controler(this, OnToneReceived);
				toneController.BeginStartup();
			}
		}

		/// <summary>
		/// Put conversation on hold
		/// </summary>
		private void HoldConversation()
		{
			// log
			EFS.EventLog.Log.LogInfo("HoldConversation", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "HoldConversation", this); 

			// get participants
			System.Collections.Generic.List<Microsoft.Rtc.Collaboration.ParticipantEndpoint> listOfParticipantEndpoints = officeServiceConference.GetRemoteParticipantEndpoints();

			// incomming routes
			System.Collections.Generic.List<Microsoft.Rtc.Collaboration.AudioVideo.IncomingAudioRoute> incomingAudioRoutes =
				new System.Collections.Generic.List<Microsoft.Rtc.Collaboration.AudioVideo.IncomingAudioRoute>();

			// find customer end point and decide routes
			foreach (Microsoft.Rtc.Collaboration.ParticipantEndpoint participantEndpoint in listOfParticipantEndpoints)
			{
				// only authorized participants
				if (officeServiceConference.IsSupervisor(participantEndpoint.Participant.Uri) || officeServiceConference.IsOperator(participantEndpoint.Participant.Uri))
				{
					// remove incomming route
					Microsoft.Rtc.Collaboration.AudioVideo.IncomingAudioRoute incomingRoute = new Microsoft.Rtc.Collaboration.AudioVideo.IncomingAudioRoute(participantEndpoint);
					incomingRoute.IsDtmfEnabled = true;
					incomingRoute.Operation = Microsoft.Rtc.Collaboration.AudioVideo.RouteUpdateOperation.Remove;

					// add
					incomingAudioRoutes.Add(incomingRoute);
				}
			}

			// check
			if (0 == incomingAudioRoutes.Count)
			{
				return;
			}

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// EFS.EventLog.Log.LogExtended("MakeB2BCall: Entering lock", this);
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Only Started Custmer Session can MakeB2BCall");
				}

				// update routes
				InsideAudioVideoCall.AudioVideoMcuRouting.BeginUpdateAudioRoutes(
					null,
					incomingAudioRoutes,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(HoldConversationEndUpdateAudioRoutes));
			}

		}

		/// <summary>
		/// Resume conversation
		/// </summary>
		private void ResumeConversation()
		{
			// log
			EFS.EventLog.Log.LogInfo("ResumeConversation", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "ResumeConversation", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// EFS.EventLog.Log.LogExtended("MakeB2BCall: Entering lock", this);
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Only Started Custmer Session can MakeB2BCall");
				}

				// add to default routing
				InsideAudioVideoCall.Conversation.ConferenceSession.AudioVideoMcuSession.BeginAddToDefaultRouting(
					ParticipantEndPoint,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(ResumeConversationAddToDefaultRoutingCompleted));
			}
		}

		/// <summary>
		/// End Add
		/// </summary>
		/// <param name="result"></param>
		private void ResumeConversationAddToDefaultRoutingCompleted(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("ResumeConversationAddToDefaultRoutingCompleted", this);

			// lock to update hold status
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			try
			{
				// try 
				InsideAudioVideoCall.Conversation.ConferenceSession.AudioVideoMcuSession.EndAddToDefaultRouting(ar);

				// resumed
				CompletedStatusTransit(ConversationCommands.Resume);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		private void HoldConversationEndUpdateAudioRoutes(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("HoldConversationEndUpdateAudioRoutes", this);
			
			// lock to update hold status
			try
			{
				// try 
				InsideAudioVideoCall.AudioVideoMcuRouting.EndUpdateAudioRoutes(ar);

				// on hold
				CompletedStatusTransit(ConversationCommands.Hold);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// completed status tranit
		/// </summary>
		private void CompletedStatusTransit(ConversationCommands newCommand)
		{
			EFS.EventLog.Log.LogInfo("CompletedStatusTransit", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check transit
				if (executingCommand != newCommand)
				{
					TransitToStatus(executingCommand);
				}
				else
				{
					// set final status
					lastCommand = executingCommand;
				
					// log
					switch (lastCommand)
					{
						case ConversationCommands.Hold:
							{
								// rtl
								EFS.RC.CC.Tools.Loggers.CHL.Add(officeServiceConference.CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Customer_Hold);
								break;
							}
						case ConversationCommands.Resume:
							{
								// rtl
								EFS.RC.CC.Tools.Loggers.CHL.Add(officeServiceConference.CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Customer_Resume);
								break;
							}
					}
				}
			}
		}

		/// <summary>
		/// Start dial out music
		/// </summary>
		private void StartDialPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("StartDialPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "StartDialPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForCalling, true);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// srart
				player.BeginStartup();

				// add
				players.Add(player);
			}
		}


		/// <summary>
		/// Start music for hold
		/// </summary>
		/// <returns></returns>
		private void StartHoldPlayer()
		{
			EFS.EventLog.Log.LogInfo("StartHoldPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "StartHoldPlayer", this);
			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForHold, true);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// start
				player.BeginStartup();

				// add to list
				players.Add(player);
			}
		}

		/// <summary>
		/// Start music for hunting agent
		/// </summary>
		/// <returns></returns>
		private void StartHuntingPlayer()
		{
			EFS.EventLog.Log.LogInfo("StartHuntingPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "StartHuntingPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForHunting, true);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// start
				player.BeginStartup();

				// add to list
				players.Add(player);
			}
		}

		/// <summary>
		/// Start music for after hours 
		/// </summary>
		/// <returns></returns>
		private void StartAfterHoursPlayer()
		{
			EFS.EventLog.Log.LogInfo("StartAfterHoursPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "StartAfterHoursPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForAfterHours, false);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// start
				player.BeginStartup();

				// add to list
				players.Add(player);
			}
		}

		/// <summary>
		/// Start music for busy
		/// </summary>
		/// <returns></returns>
		private void StartBusyPlayer()
		{
			EFS.EventLog.Log.LogInfo("StartBusyPlayer", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "StartBusyPlayer", this);

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// stop
				StopPlayers();

				// start on hold music
				EFS.RC.CC.Tools.Players.Audio player = new EFS.RC.CC.Tools.Players.Audio(this, Config.Settings.MusicForOfficeBusy, false);
				player.AsinkTerminatedDelegate.Register(OnPlayerTerminated);

				// start
				player.BeginStartup();

				// add to list
				players.Add(player);
			}
		}

		/// <summary>
		/// Start misic for hunting agent
		/// </summary>
		/// <returns></returns>
		private void StopPlayers()
		{
			EFS.EventLog.Log.LogInfo("StopPlayers", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "StopPlayers", this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// stop all playes if any
				foreach (EFS.RC.CC.Tools.Players.Audio player in players)
				{
					// stop
					player.BeginShutdown();
				}
			}
		}

		/// <summary>
		/// On music terminated
		/// </summary>
		/// <param name="player"></param>
		private void OnPlayerTerminated(EFS.RC.CC.Tools.Players.Audio player)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnPlayerTerminated", this);

			// check
			EFS.Common.Verify.Reference(player, "player");

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (players.Contains(player))
				{
					// remove
					players.Remove(player);
				}
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Remove customer from office conference
		/// </summary>
		protected abstract void CleanupSession();

		/// <summary>
		/// Shut down
		/// </summary>
		public void Shutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("Shutdown", this);

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (status != SessionStatus.Terminated)
				{
					// set terinated
					status = SessionStatus.Terminated;

					// notify terminate
					AsinkTerminatedDelegate.Start();
				}
				else
				{
					// already terminated
					return;
				}
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "Shutdown", this); 

			// log
			EFS.RC.CC.Tools.Loggers.CHL.Add(conversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Customer_Terminate);

			// call remove
			BeginAsyncProcess2(CleanupSession);
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnShutdownComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
